/*
 * @(#) $Header$
 *
 * Copyright (C) 2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.io;

import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import ca.forklabs.baselib.util.Algorithm;
import ca.forklabs.baselib.util.Iterators;
import ca.forklabs.baselib.util.UnaryFunction;
import ca.forklabs.baselib.io.AbstractWriter;

import java.io.IOException;

/**
 * Class {@link ForkWriter} is a writer that has multiple sinks. All methods
 * synchronize on the internal lock unless stated otherwise.
 * <p>
 * Methods in this class never throw I/O exceptions.  The client may inquire as
 * to whether any errors have occurred by invoking {@link #checkError()}.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.io.ForkWriter">Daniel Léonard</a>
 * @version $Revision$
 */
public class ForkWriter extends AbstractWriter {

//---------------------------
// Instance variables
//---------------------------

   /** The sink. */
   private List<Writer> sinks = new LinkedList<>();

   /** Tells if an error occurred. */
   private boolean has_error_occured = false;


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    */
   public ForkWriter() {
   // nothing
      }

   /**
    * Constructor.
     * @param   lock   the lock object.
    */
   public ForkWriter(Object lock) {
      super(lock);
      }

   /**
    * Constructor.
    * @param   writers   a collection of writers.
    */
   public ForkWriter(Iterable<Writer> writers) {
      this.addAll(writers);
      }

    /**
     * Constructor. The critical sections will synchronized on the given lock.
     * @param   writers   a collection of writers.
     * @param   lock   the lock object.
     */
    public ForkWriter(Iterable<Writer> writers, Object lock) {
       this(lock);
       this.addAll(writers);
       }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Gets the list of sinks.
    * @return   the sinks.
    */
   protected List<Writer> getSinks() {
      return this.sinks;
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets an {@link Iterator} on all the sinks. This method does not
    * synchronizes on the internal lock.
    * @return   the iterator.
    */
   protected Iterator<Writer> getSinkIterator() {
      List<Writer> writers = this.getSinks();
      Iterator<Writer> iterator = writers.iterator();
      return iterator;
      }

   /**
    * Adds a writer.
    * @param   writer   the writer.
    */
   @SuppressWarnings("hiding")
   public void add(Writer writer) {
      Object lock = this.getLock();
      synchronized (lock) {
         List<Writer> writers = this.getSinks();
         writers.add(writer);
         }
      }

   /**
    * Adds many writers.
    * @param   writers   the writers.
    */
   @SuppressWarnings("hiding")
   public void addAll(Iterable<Writer> writers) {
      Object lock = this.getLock();
      synchronized (lock) {
         Algorithm.copy(writers.iterator(), Iterators.backInserter(this.getSinks()));
         }
      }

   /**
    * Removes the given writer.
    * @param   writer   the writer.
    */
   @SuppressWarnings("hiding")
   public void remove(Writer writer) {
      Object lock = this.getLock();
      synchronized (lock) {
         List<Writer> writers = this.getSinks();
         writers.remove(writer);
         }
      }

   /**
    * Check the writer for its error state. Errors are cumulative; once the
    * writer encounters an error, this routine will return {@code true} on all
    * successive calls.
    * @return    {@code true} if the writer has encountered an error,
    *            {@code false} oterwise.
    */
   public boolean checkError() {
      return this.has_error_occured;
      }

   /**
    * Indicate that an error has occurred.
    */
   protected void setError() {
      this.has_error_occured = true;
      }


//---------------------------
// Overridden methods from java.io.Writer
//---------------------------

   /**
    * Write the same portion of an array of characters into each sink.
    * @param   cbuf  array of characters.
    * @param   off   offset from which to start writing characters.
    * @param   len   number of characters to write.
    */
   @Override
   @SuppressWarnings("hiding")
   public void write(char[] cbuf, int off, int len) {
      Object lock = this.getLock();
      synchronized (lock) {
         Iterator<Writer> iter = this.getSinkIterator();

         final char[] buffer = cbuf;
         final int offset = off;
         final int size = len;

         Algorithm.forEach(iter, new UnaryFunction<Void, Writer>() {
            @Override
            @SuppressWarnings("resource")
            public Void invoke(Writer writer) {
               try {
                  writer.write(buffer, offset, size);
                  }
               catch (IOException ioe) {
                  ForkWriter fork_writer = ForkWriter.this;
                  fork_writer.setError();
                  }
               return null;
               }
            });
         }
      }

   /**
    * Flushes every sink.
    */
   @Override
   @SuppressWarnings("hiding")
   public void flush() {
      Object lock = this.getLock();
      synchronized (lock) {
         Iterator<Writer> iter = this.getSinkIterator();
         Algorithm.forEach(iter, new UnaryFunction<Void, Writer>() {
            @Override
            @SuppressWarnings("resource")
            public Void invoke(Writer writer) {
               try {
                  writer.flush();
                  }
               catch (IOException ioe) {
                  ForkWriter fork_writer = ForkWriter.this;
                  fork_writer.setError();
                  }
               return null;
               }
            });
         }
      }


//---------------------------
// Overridden methods from ca.forklabs.baselib.io.AbstractWriter
//---------------------------

   /**
    * Closes this writer and every sink.
    */
   @Override
   @SuppressWarnings("hiding")
   public void close() {
      Object lock = this.getLock();
      synchronized (lock) {
         Iterator<Writer> iter = this.getSinkIterator();
         Algorithm.forEach(iter, new UnaryFunction<Void, Writer>() {
            @Override
            @SuppressWarnings("resource")
            public Void invoke(Writer writer) {
               try {
                  writer.close();
                  }
               catch (IOException ioe) {
                  ForkWriter fork_writer = ForkWriter.this;
                  fork_writer.setError();
                  }
               return null;
               }
            });

         super.close();
         }
      }

   }
